local L = LibStub("AceLocale-3.0"):GetLocale("AzCastBarOptions")
local GetTime = GetTime;
local FormatTime = AzCastBar.FormatTime;

-- Extra Options
local extraOptions = {
	{
		[0] = L["Additional"],
		{ type = "Check", var = "showRank", default = true, label = L["Show Spell Rank"], tip = L["If the spell being cast has a rank, it will be shown in brackets after the spell name."], y = 16 },
		{ type = "Color", var = "colNormal", default = { 0.4, 0.6, 0.8, 1.0 }, label = L["Normal Cast Color"] },
		{ type = "Color", var = "colFailed", default = { 1.0, 0.5, 0.5, 1.0 }, label = L["Failed Cast Bar Color"] },
		{ type = "Color", var = "colInterrupt", default = { 1.0, 0.75, 0.5, 1.0 }, label = L["Interrupted Cast Bar Color"], y = 16 },
		{ type = "Check", var = "safeZone", default = false, label = L["Show Safe Zone Area"], tip = L["The 'Safe Zone' is the time equal to your latency, with this option enabled, it will show this duration on the cast bar. A spell canceled after it has reached the safe zone, will still go off."], restrict = { "Player" }, y = 6 },
		{ type = "Color", var = "colSafezone", default = {0.3,0.8,0.3,0.6}, label = L["Safe Zone Color"], restrict = { "Player" }, y = 20 },
		{ type = "Check", var = "mergeTrade", default = false, label = L["Merge Tradeskill Cast Times"], tip = L["Will show the combined time it takes to craft all items"], restrict = { "Player" } },
	},
};

-- Casting Bar Events
local events = {
	"PLAYER_ENTERING_WORLD",
	"UNIT_SPELLCAST_START",
	"UNIT_SPELLCAST_STOP",
	"UNIT_SPELLCAST_FAILED",
	"UNIT_SPELLCAST_INTERRUPTED",
	"UNIT_SPELLCAST_DELAYED",
	"UNIT_SPELLCAST_CHANNEL_START",
	"UNIT_SPELLCAST_CHANNEL_UPDATE",
	"UNIT_SPELLCAST_CHANNEL_STOP",
};

-- Spell Names for Hearthstone & Astral Recall
local astral = GetSpellInfo(556);
local hearth = GetSpellInfo(8690);

-- Trade Hook
local tradeCountTotal, allowTradeMerge;
hooksecurefunc("DoTradeSkill",function(index,num) if (allowTradeMerge) then tradeCountTotal = num; end end);

--------------------------------------------------------------------------------------------------------
--                                              OnUpdate                                              --
--------------------------------------------------------------------------------------------------------

local function OnUpdate(self,elapsed)
	-- Update Bar (Only player unit seems to give proper UNIT_SPELLCAST_STOP event, so start fadeout when cast completes)
	if (not self.fadeTime) then
		self.timeProgress = min(GetTime() - self.startTime,self.castTime);
		self.timeLeft = (self.castTime - self.timeProgress);

		self.status:SetValue(self.isCast and self.timeProgress or self.timeLeft);
		self.time:SetText(self.delayText..FormatTime(self.timeLeft)..(self.cfg.showTotalTime and " / "..FormatTime(self.castTime,1) or ""));

		if (self.safezone) and (self.safezone:IsVisible()) then
			self.safeZonePercent = min(1,select(3,GetNetStats()) / 1000 / self.castTime);
			self.safezone:SetWidth(self.safeZonePercent * self.status:GetWidth());
		end

		if (self.timeLeft == 0) and (not self.tradeCount or self.tradeCount == tradeCountTotal) then
			self:StartFadeOut();
		end
	-- FadeOut
	elseif (self.fadeElapsed <= self.fadeTime) then
		self.fadeElapsed = (self.fadeElapsed + elapsed);
		self:SetAlpha(self.cfg.alpha - self.fadeElapsed / self.fadeTime * self.cfg.alpha);
	else
		self.isCast, self.isChannel = nil, nil;
		self:Hide();
	end
end

--------------------------------------------------------------------------------------------------------
--                                           Event Handling                                           --
--------------------------------------------------------------------------------------------------------
local function OnEvent(self,event,p1,...)
	-- Entering World + Target/Focus Change
	if (event == "PLAYER_TARGET_CHANGED" or event == "PLAYER_FOCUS_CHANGED" or event == "PLAYER_ENTERING_WORLD") then
		if (UnitCastingInfo(self.unit)) then
			event = "UNIT_SPELLCAST_START";
			p1 = self.unit;
		elseif (UnitChannelInfo(self.unit)) then
			event = "UNIT_SPELLCAST_CHANNEL_START";
			p1 = self.unit;
		else
			self:Hide();
			return;
		end
	end

	-- Check Unit
	if (self.unit ~= p1) then
		return;
	-- Use the Player Bar for Possessed Pets/Vehicles
	elseif (self.unit == "pet") and (UnitIsPossessed("pet")) then
		-- Az: Would it be faster to just access the bar by global name than scanning the "AzCastBar.frames" table?
--		self = AzCastBarPluginPlayer;
		for index, frame in ipairs(AzCastBar.frames) do
			if (frame.unit == "player") then
				self = frame;
				break;
			end
		end
	end

	-- Start
	if (event == "UNIT_SPELLCAST_START" or event == "UNIT_SPELLCAST_CHANNEL_START") then
		-- Init Vars
		self.isCast = (event == "UNIT_SPELLCAST_START");
		self.isChannel = (event == "UNIT_SPELLCAST_CHANNEL_START");
		self.spell, self.rank, self.label, self.iconPath, self.startTime, self.endTime, self.isTrade, self.id = (self.isCast and UnitCastingInfo or UnitChannelInfo)(p1);
		self.startTime, self.endTime = (self.startTime / 1000), (self.endTime / 1000);
		self.castTime = (self.endTime - self.startTime);
		-- Player Specific
		if (self.unit == "player") then
			if (self.cfg.mergeTrade) and (self.isTrade) and (tradeCountTotal) and (tradeCountTotal > 1) then
				if (not self.tradeCount) then
					self.tradeCount = 1;
					self.tradeStart = self.startTime;
					self.castTime = ((self.castTime + 0.5) * tradeCountTotal);
				else
					self.castTime = (GetTime() - self.tradeStart) / self.tradeCount * tradeCountTotal;
					self.tradeCount = (self.tradeCount + 1);
				end
				self.startTime = self.tradeStart;
				self.endTime = (self.startTime + self.castTime);
				self.spell = self.spell.." ("..self.tradeCount.."/"..tradeCountTotal..")";
			end
			if (self.spell == hearth or self.spell == astral) then
				self.rank = GetBindLocation();
			end
			if (self.cfg.safeZone) then
				self.safezone:ClearAllPoints();
				self.safezone:SetPoint(self.isCast and "TOPRIGHT" or "TOPLEFT");
				self.safezone:SetPoint(self.isCast and "BOTTOMRIGHT" or "BOTTOMLEFT");
				self.safezone:Show();
			end
		end
		-- Init Objects
		self.status:SetMinMaxValues(0,self.castTime);
		self.status:SetStatusBarColor(unpack(self.cfg.colNormal));
		self.name:SetText(self.cfg.showRank and self.rank ~= "" and self.spell.." ("..self.rank..")" or self.spell);
		self.icon:SetTexture(self.iconPath);
		-- Set State and Show
		self.fadeTime = nil;
		self.fadeElapsed = 0;
		self.castDelay = 0;
		self.delayText = "";
		self:SetAlpha(self.cfg.alpha);
		self:Show();
	-- Quit if Hidden
	elseif (not self:IsShown()) then
		return;
	-- Failed (Happens after UNIT_SPELLCAST_STOP, so keep them here)
	elseif (event == "UNIT_SPELLCAST_FAILED") and (not self.isChannel) and (self.id == select(3,...)) then
		self.status:SetValue(self.castTime);
		self.status:SetStatusBarColor(unpack(self.cfg.colFailed));
		self.time:SetText(FAILED);
		self:StartFadeOut();
	-- Interrupted (Happens after UNIT_SPELLCAST_STOP, so keep them here)
	elseif (event == "UNIT_SPELLCAST_INTERRUPTED") and (not self.isChannel) then
		self.status:SetValue(self.castTime);
		self.status:SetStatusBarColor(unpack(self.cfg.colInterrupt));
		self.time:SetText(INTERRUPTED);
		self:StartFadeOut();
	-- Quit here if no Cast or Channel is in Progress, or we are showing a merged trade cast
	elseif (self.fadeTime or self.tradeCount) then
		return;
	-- Stop (Happens even with instant spells & abilities) (UNIT_SPELLCAST_STOP gets called right after UNIT_SPELLCAST_CHANNEL_START for some reason) (Doesn't always seem to trigger when target or focus completes cast)
	elseif (event == "UNIT_SPELLCAST_STOP" and self.isCast) or (event == "UNIT_SPELLCAST_CHANNEL_STOP" and self.isChannel) then
		self.status:SetValue(self.isCast and self.castTime or 0);
		self:StartFadeOut();
	-- Cast Delayed
	elseif (event == "UNIT_SPELLCAST_DELAYED" or event == "UNIT_SPELLCAST_CHANNEL_UPDATE") then
		local startTimeNew, endTimeNew = select(5,(event == "UNIT_SPELLCAST_DELAYED" and UnitCastingInfo or UnitChannelInfo)(self.unit));
		if (startTimeNew and endTimeNew) then
			local endTimeOld = self.endTime;
			self.startTime, self.endTime = (startTimeNew / 1000), (endTimeNew / 1000);
			self.castDelay = (self.castDelay + self.endTime - endTimeOld);
			self.delayText = format("|cffff8080%s%.1f|r  ",self.castDelay > 0 and "+" or "",self.castDelay);
		end
	end
end

--------------------------------------------------------------------------------------------------------
--                                             Setup Bars                                             --
--------------------------------------------------------------------------------------------------------

-- Config Changed
local function OnConfigChanged(self,cfg,barId)
	-- For All CastBars
	if (cfg.enabled) then
		for _, event in ipairs(events) do
			self:RegisterEvent(event);
		end
		if (self.unit == "target") then
			self:RegisterEvent("PLAYER_TARGET_CHANGED");
		elseif (self.unit == "focus") then
			self:RegisterEvent("PLAYER_FOCUS_CHANGED");
		end
		OnEvent(self,"PLAYER_ENTERING_WORLD");
	else
		self:UnregisterAllEvents();
		self:Hide();
	end
	-- For Player + Pet
	if (self.unit == "player" or self.unit == "pet") then
		if (self.unit == "player") then
			tradeCountTotal = nil;
			allowTradeMerge = (cfg.enabled and cfg.mergeTrade);
		end
		local frame = (self.unit == "player" and CastingBarFrame or PetCastingBarFrame);
		if (cfg.enabled) then
			if (self.safezone) then
				self.safezone:SetTexture(unpack(cfg.colSafezone));
			end
			frame:UnregisterAllEvents();
			frame.showCastbar = nil;
		else
			frame.showCastbar = (self.unit == "player" and true or UnitIsPossessed("pet"));
			for _, event in ipairs(events) do
				frame:RegisterEvent(event);
			end
			if (self.unit == "pet") then
				frame:RegisterEvent("UNIT_PET");
			end
		end
	end
end

-- Start Frame FadeOut
local function StartFadeOut(self)
	if (not self.fadeTime) then
		self.tradeCount = nil;
		self.fadeTime = self.cfg.fadeTime;
		if (self.safezone) then
			self.safezone:Hide();
		end
	end
end

-- Init Bars
do
	local bars = { "Player", "Target", "Focus", "Pet" };  -- Az: Could add "vehicle" to this table, but "pet" seems to handle it just fine?
	local bar, lastBar;
	for _, token in ipairs(bars) do
		bar = AzCastBar.CreateBar();
		-- Init Bar
		bar.token = token;
		bar.unit = token:lower();
		bar.options = extraOptions;
		tinsert(AzCastBar.frames,bar);
		bar.StartFadeOut = StartFadeOut;
		-- Anchor
		if (lastBar) then
			bar:SetPoint("TOP",lastBar,"BOTTOM",0,-8);
		else
			bar:SetPoint("CENTER",0,-100);
		end
		lastBar = bar;
		-- Events
		for _, event in ipairs(events) do
			bar:RegisterEvent(event);
		end
		bar:SetScript("OnEvent",OnEvent);
		bar:SetScript("OnUpdate",OnUpdate);
		bar.OnConfigChanged = OnConfigChanged;
		-- Create Safezone
		if (token == "Player") then
			bar.safezone = bar.status:CreateTexture(nil,"OVERLAY");
			bar.safezone:SetTexture(0.3,0.8,0.3,0.6);
			bar.safezone:SetPoint("TOPRIGHT");
			bar.safezone:SetPoint("BOTTOMRIGHT");
			bar.safezone:Hide();
		end
	end
end